
package hujz.java.file_server.http;

import hujz.java.file_server.*;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import io.netty.util.AttributeKey;
import io.netty.util.ReferenceCountUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.Principal;
import java.text.SimpleDateFormat;
import java.util.Date;

import static hujz.java.file_server.Configuration.INSTANCE;

public class HttpUploadServerHandler extends ChannelInboundHandlerAdapter {

    private static final Logger LOGGER = LoggerFactory.getLogger(HttpUploadServerHandler.class);

    private HttpRequest request;
    private final HttpResponse response;

    private static final HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); // Disk if size exceed

    private HttpPostRequestDecoder decoder;

    static {
        DiskFileUpload.deleteOnExitTemporaryFile = true; // should delete file
        DiskFileUpload.baseDirectory = INSTANCE.getTempDir(); // system temp directory
    }

    private StorageInterface storage = INSTANCE.getStorageImpl();
    private FileMetaSaveInterface fileMetaSave = INSTANCE.getFileMetaSaveImpl();

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        if (decoder != null) {
            decoder.cleanFiles();
        }
        LOGGER.info("[FileServer] <-- end upload file.");
    }

    public HttpUploadServerHandler(HttpRequest request, HttpResponse response) {
        this.request = request;
        this.fileId = request.headers().get("fileId");
        decoder = new HttpPostRequestDecoder(factory, request);
		this.response = response;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        try {
            LOGGER.trace("[FileServer] --- coming upload file.");
            channelRead0(ctx, (HttpObject) msg);
            if (msg instanceof LastHttpContent) {
                FileMeta fileMeta = new FileMeta();
                fileMeta.setCreateTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                fileMeta.setFileName(fileName);

                io.netty.util.Attribute<Principal> principalAttribute = ctx.channel().attr(AttributeKey.valueOf("Principal"));
                Principal principal = principalAttribute.get();
                if (principal != null) fileMeta.setOwner(principal.getName());

                fileMeta.setSecret("0");
                fileMeta.setMediaType(contentType);
                fileMeta.setStatus("0");

                if (fileUpload.isInMemory()) {
                    storage.storage(fileMeta, new ByteArrayInputStream(fileUpload.get()));
                } else {
                    storage.storage(fileMeta, fileUpload.getFile());
                }

                fileMetaSave.save(fileMeta);

                FileServerResponse resp = new FileServerResponse();
                resp.setS("1");
                resp.setR(new FileServerResponse.FileInfo());
                resp.getR().setFileId(fileMeta.getUuid());
                resp.getR().setFullFileId(fileMeta.getUuid());
                resp.getR().setMetadata(new FileServerResponse.MetaData());
                resp.getR().getMetadata().setBytes(fileMeta.getSize());
                resp.getR().getMetadata().setHash(fileMeta.getSign());
                resp.getR().getMetadata().setName(fileMeta.getFileName());
                String respStr = JsonUtil.objectMapper.writeValueAsString(resp);

                FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_0, HttpResponseStatus.OK, Unpooled.wrappedBuffer(respStr.getBytes("utf8")), response.headers(), EmptyHttpHeaders.INSTANCE);
                fullHttpResponse.headers().set("Content-Type", "application/json");
                
                ctx.channel().writeAndFlush(fullHttpResponse);
                ctx.channel().flush();
                ctx.channel().close();

                reset();
                LOGGER.info("[FileServer] <-- complete upload file, fileName: \"{}\".", fileMeta.getFileName());
            }
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        if (msg instanceof HttpContent) {
            HttpContent chunk = (HttpContent) msg;
            decoder.offer(chunk);
            readHttpDataChunkByChunk();
        }
    }

    private void reset() {
        request = null;
        decoder.destroy();
        decoder = null;
    }

    private void readHttpDataChunkByChunk() {
        while (decoder.hasNext()) {
            InterfaceHttpData data = decoder.next();
            if (data != null) {
                readHttpData(data);
            }
        }
    }

    private String fileName;
    private long size;
    private String fileId;
    private String contentType;

    FileUpload fileUpload;

    private void readHttpData(InterfaceHttpData data) {
        if (data.getHttpDataType() == HttpDataType.Attribute) {
            try {
                Attribute attribute = (Attribute) data;
                switch (attribute.getName()) {
                    case "fileName":
                        if (StringUtils.isNotEmpty(attribute.getValue()))
                            this.fileName = attribute.getValue();
                        break;
                    case "size":
                        if (StringUtils.isNumeric(attribute.getValue())) {
                            long _size = Long.parseLong(attribute.getValue());
                            if (size == 0)
                                size = _size;
                        }
                        break;
                }
            } catch (IOException e) {
                LOGGER.error(e.getMessage(), e);
            }
        } else if (data.getHttpDataType() == HttpDataType.FileUpload && ((FileUpload) data).isCompleted()) {
            fileUpload = (FileUpload) data;
            if (fileUpload.isCompleted()) {
                contentType = fileUpload.getContentType();
                if (fileName == null)
                    fileName = fileUpload.getFilename();
                if (size == 0)
                    size = fileUpload.length();
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        LOGGER.error("[FileServer] --- Update file failed!", cause);
        FileServerResponse resp = new FileServerResponse();
        resp.setS("0");
        resp.setM(cause.getMessage());
        String respStr = JsonUtil.objectMapper.writeValueAsString(resp);

        FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_0, HttpResponseStatus.OK, Unpooled.wrappedBuffer(respStr.getBytes("utf8")), response.headers(), EmptyHttpHeaders.INSTANCE);
        fullHttpResponse.headers().add("Content-Type", "application/json");
        ctx.channel().writeAndFlush(fullHttpResponse).addListener(ChannelFutureListener.CLOSE);
        ctx.flush();
        ctx.channel().close();
        LOGGER.info("[FileServer] <-- Upload file error, fileName: \"{}\"!", fileName);
    }
}